Accept an expected consumption of zero - #61
Conversation
Two falsy checks treated a numeric zero as an absent value, so a caller who named a limit and declared it costs nothing was told they had not named it at all. The same check on the sum also refused an ordinary positive consumption whenever the usage level was still zero, which is the state of every contract on its first call and after every renewal.
|
The red Integration Tests Run check on this PR is not caused by the change. The job reads its Mongo port and database name from the The workflow has never passed on a fork PR — every green run in its history came from a branch inside the repository. #62 fixes it, and its own check is green, which is the fix running on a fork PR. Once that lands this check should go green here too. |
|
Full suite, verified locally. Since the workflow cannot run on a fork PR until #62 lands, I reproduced the CI environment (Mongo 7.0.16 on 27017, Redis 7 on 6379, an That is the repository's 12 files plus the one this PR adds. The same run on One note on method, since it changed a conclusion: an earlier run of mine reported failures in |
The problem
expectedConsumptioncannot express "this limit takes part in the evaluation, but this call spends nothing of it".When a caller provides
expectedConsumption,evaluateFeaturerequires a value for every limit involved in the feature's expression, or it refuses the call:So the only value a caller can give for a limit they are not spending is
0. Two falsy checks infeatureEvaluation.tsrejected exactly that, and reported it with the message meant for a caller who forgot the limit entirely.The second check is the wider bug: it tests the sum, not the input, so it also refuses an ordinary positive consumption whenever the current usage level is still
0— the state of every contract on its first call, and of every renewable limit right after it resets.Reproduction
A feature whose expression involves one limit, evaluated with
simple: false:{ maxPets: 0 }used: 5, no errorINVALID_EXPECTED_CONSUMPTION{ maxPets: 0 }used: 0, no errorINVALID_EXPECTED_CONSUMPTION{ maxPets: 1 }used: 1, no error0 + 1is truthy)The first row is the "check without charging" case — asking whether an action would be allowed. The second is the same request against a contract that has not consumed anything yet. Neither has anything to do with a missing value, which is what the caller is told.
This also affects any feature whose expression involves several limits where only some are actually spent: the caller must name them all, and the ones that cost nothing can only be declared as zero.
The change
Two lines in
api/src/main/utils/feature-evaluation/featureEvaluation.ts:_updateUsageLeveldistinguishes an absent value from zero:expectedConsumption === undefined || expectedConsumption === nullinstead of!expectedConsumption._buildSuccessResultchecks the returned value forundefinedinstead of for falsiness, so a computed usage level of0is a result, not a failure.The genuinely-missing case is unchanged: a limit absent from
expectedConsumptionstill yieldsINVALID_EXPECTED_CONSUMPTION, and the separatethrowinevaluateFeaturethat guards the write path is untouched.Verification
New file
api/src/test/feature-evaluation.zero-consumption.test.ts— 8 tests, driving the publicevaluateFeaturerather than the private helpers:Each of the two source changes was reverted independently to confirm the tests are load-bearing: without the
_buildSuccessResultfix, 2 of the 8 fail; without the_updateUsageLevelfix, 3 fail.The existing suite for this module is unaffected:
npx tsc --noEmitis clean.Scope
Two lines plus a test file. No API shape, no schema and no persisted data changes; a request that worked before still works, and the newly accepted values previously produced an error rather than a different result — so nothing that currently succeeds changes behaviour.